#include "stdafx.h"
#include "provider.h"
#include "debug.h"
#include "helper.h"

#include "curl/curl.h"
#include "curl/types.h"
#include "curl/easy.h"
#include "deelx.h"


FOOGUIDDECL pfc::list_t<lyric_provider_ptr> g_lyric_provider_list;

class lyrics_grabber_initquit: public initquit
{
public:
	void on_init()
	{
		// enumerate services
		service_enum_t<lyric_provider_base> e;
		lyric_provider_ptr ptr;

		g_lyric_provider_list.remove_all();
		if (e.first(ptr)) do 
		{
			ptr->get_global_config(&get_lyrics_grabber_config());
			g_lyric_provider_list.add_item(ptr);
		} while (e.next(ptr));
	}
	void on_quit() 
	{
		//g_lyric_provider_list.remove_all();
	}
};


initquit_factory_t<lyrics_grabber_initquit> g_initquit;
service_factory_single_t<lyric_provider_lyricwiki> g_lyricwiki;
service_factory_single_t<lyric_provider_lyricsplugin> g_lyricsplugin;


// {DA17F945-6948-4359-BCF5-BA746AE5196E}
FOOGUIDDECL const GUID lyric_provider_base::class_guid = 
{ 0xda17f945, 0x6948, 0x4359, { 0xbc, 0xf5, 0xba, 0x74, 0x6a, 0xe5, 0x19, 0x6e } };

// {FAC2C609-6228-4827-8694-4A1F9F77771C}
FOOGUIDDECL const GUID lyric_provider_lyricwiki::provider_guid = 
{ 0xfac2c609, 0x6228, 0x4827, { 0x86, 0x94, 0x4a, 0x1f, 0x9f, 0x77, 0x77, 0x1c } };

// {F498CC9E-0D41-4f63-B0DD-90F287B35B88}
FOOGUIDDECL const GUID lyric_provider_lyricsplugin::provider_guid = 
{ 0xf498cc9e, 0xd41, 0x4f63, { 0xb0, 0xdd, 0x90, 0xf2, 0x87, 0xb3, 0x5b, 0x88 } };


size_t lyric_provider_base::g_write_memory_callback( void *ptr, size_t size, size_t nmemb, void *data )
{
	size_t realsize = size * nmemb;
	pfc::string_base *str = reinterpret_cast<pfc::string_base *>(data);

	if (str) 
		str->add_string(reinterpret_cast<const char *>(ptr), realsize);

	return realsize;
}

void lyric_provider_lyricwiki::query(metadb_handle_list_cref p_meta, pfc::string_list_impl & p_data, threaded_process_status & p_status, abort_callback & p_abort)
{
	// CURL Handle
	CURL * curl_handle;

	// Regular Expression Class
	CRegexpT<char> regexp;
	MatchResult match;

	// Buffer
	pfc::string8 buff;

	p_data.remove_all();
	p_status.set_title("Querying LyricWiki...");
	
	if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)
	{
		console_error("curl_global_init() faield");
		return;
	}

	// init curl session
	curl_handle = curl_easy_init();

	if (!curl_handle)
	{
		console_error("curl_easy_init() faield");
		return;
	}

	// Basic curl setting
	// Set Proxy
	if (m_config_item->proxy_type != PROXY_NONE)
	{
		curl_proxytype proxy_type;

		switch (m_config_item->proxy_type)
		{
		case PROXY_SOCKS4:
			proxy_type = CURLPROXY_SOCKS4;
			break;

		case PROXY_SOCKS5:
			proxy_type = CURLPROXY_SOCKS5;
			break;

		case PROXY_HTTP:
		default:
			proxy_type = CURLPROXY_HTTP;
			break;
		}

		// proxy type
		curl_easy_setopt(curl_handle, CURLOPT_PROXYTYPE, proxy_type);

		// proxy address:port
		curl_easy_setopt(curl_handle, CURLOPT_PROXY, m_config_item->proxy_hostport.get_ptr());

		if (m_config_item->proxy_need_auth)
		{
			pfc::string8_fast proxy = m_config_item->proxy_username;

			proxy.add_char(':');
			proxy.add_string(m_config_item->proxy_password);
			curl_easy_setopt(curl_handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
			curl_easy_setopt(curl_handle, CURLOPT_PROXYUSERPWD, proxy.get_ptr());
		}
	}

	// Set Timeout for connect
	// 10 sec
	curl_easy_setopt(curl_handle, CURLOPT_CONNECTTIMEOUT, 10);

	// Set low speed
	curl_easy_setopt(curl_handle, CURLOPT_LOW_SPEED_LIMIT, (long)0); 
	curl_easy_setopt(curl_handle, CURLOPT_LOW_SPEED_TIME, (long)15);

	// send all data to this function
	curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, g_write_memory_callback);

	// we pass our 'chunk' struct to the callback function 
	curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &buff);

	for (t_size i = 0; i < p_meta.get_count(); ++i)
	{
		if (p_abort.is_aborting())
			break;

		// Sleep 100ms;
		Sleep(100);

		// Clear buff
		buff.reset();

		const metadb_handle_ptr & p = p_meta.get_item(i);
	
		if (p.is_empty())
		{
			p_data.add_item("");
			continue;
		}

		// Set progress
		pfc::string8_fast path = file_path_canonical(p->get_path());
		
		// add subsong index?
		if (p->get_subsong_index() > 0)
		{
			path.add_string(" /index:");
			path.add_string(pfc::format_uint(p->get_subsong_index()));
		}

		p_status.set_item_path(path);
		p_status.set_progress(i + 1, p_meta.get_count());

		pfc::string8_fast artist, album, title;
		static_api_ptr_t<titleformat_compiler> compiler;
		service_ptr_t<titleformat_object> script;

		// Get ARTIST
		compiler->compile_safe(script, "$replace($caps2(%artist%),' ','_')");
		p->format_title(NULL, artist, script, NULL);
		// Get ALBUM
		compiler->compile_safe(script, "[%album%]");
		p->format_title(NULL, album, script, NULL);
		// Get TITLE
		compiler->compile_safe(script, "$replace($caps2(%title%),' ','_')");
		p->format_title(NULL, title, script, NULL);

		// Fetching from HTTP
		// Set HTTP Address
		pfc::string8_fast url("http://lyricwiki.org/");
		char * pArtist = NULL, * pTitle = NULL;

		pArtist = curl_easy_escape(curl_handle, artist, 0);
		pTitle = curl_easy_escape(curl_handle, title, 0);
		url += pArtist;
		url += ":";
		url += pTitle;
		curl_free(pArtist);
		curl_free(pTitle);
		curl_easy_setopt(curl_handle, CURLOPT_URL, url.get_ptr());

		// Get it now
		CURLcode nCode = curl_easy_perform(curl_handle);

		if(nCode == CURLE_OK)
		{
			struct t_ti_href
			{
				const char * pos_ti, * pos_href;
				t_size len_ti, len_href;
			};

			const char * regex_lyricbox = "<div\\s+class\\s*=\\s*'lyricbox'\\s*>\\s*(.+?)</div>";

			// codes below are  *REALLY UGLY* :(
			// Get first Heading
			regexp.Compile("class=\"firstHeading\">.+?:(.+?)</");
			match = regexp.Match(buff.get_ptr());

			// should not fail
			if (!match.IsMatched())
			{
				p_data.add_item("");
				continue;
			}
		
			pfc::list_t<t_ti_href> sub_list;

			// Check toc title to find child
			if (buff.find_first("id=\"toctitle\">") != infinite)
			{
				pfc::string8 lyric;

				// Extract all subs and href
				regexp.Compile("class=\"toclevel-1\"><a\\s+href=\"#(.+?)\"><.+?class=\"toctext\">(\\w+\\..+?)</span>");
				match = regexp.Match(buff.get_ptr());

				if (match.IsMatched())
				{
					while (match.IsMatched())
					{
						t_ti_href title_addr;

						title_addr.pos_href = buff.get_ptr() + match.GetGroupStart(1);
						title_addr.pos_ti = buff.get_ptr() + match.GetGroupStart(2);
						title_addr.len_href = match.GetGroupEnd(1) - match.GetGroupStart(1);
						title_addr.len_ti = match.GetGroupEnd(2) - match.GetGroupStart(2);
						sub_list.add_item(title_addr);

						match = regexp.Match(buff.get_ptr(), match.GetEnd());
					}
				}
				else
				{
					// expression for extract lyrics
					regexp.Compile(regex_lyricbox, SINGLELINE);

					// match
					MatchResult result = regexp.Match(buff.get_ptr());

					// Get Group
					if (result.IsMatched())
					{
						int nStart = result.GetGroupStart(1);
						int nEnd = result.GetGroupEnd(1);

						pfc::string8 lyric(buff.get_ptr() + nStart, nEnd - nStart);
						convert_html_to_plain(lyric);
						p_data.add_item(lyric.get_ptr());
					}

					continue;
				}

				regexp.Compile(regex_lyricbox, SINGLELINE);

				// enumerate all titles
				for (t_size i = 0; i < sub_list.get_count(); ++i)
				{
					lyric.add_string(sub_list[i].pos_ti, sub_list[i].len_ti);
					lyric.add_string("<br/><br/>");

					t_size start, length;

					pfc::string8_fast to_find;

					to_find.set_string("name=\"");
					to_find.add_string(sub_list[i].pos_href, sub_list[i].len_href);
					to_find.add_string("\"");
					start = buff.find_last(to_find);

					if (i == sub_list.get_count() - 1)
					{
						length = buff.length();
					}
					else
					{
						to_find.set_string("name=\"");
						to_find.add_string(sub_list[i + 1].pos_href, sub_list[i + 1].len_href);
						to_find.add_string("\"");

						length = buff.length() - buff.find_last(to_find.get_ptr());
					}

					match = regexp.Match(buff.get_ptr(), length, start);

					if (match.IsMatched())
					{
						lyric.add_string(buff.get_ptr() + match.GetGroupStart(1), 
							match.GetGroupEnd(1) - match.GetGroupStart(1));
						lyric.add_string("<br/><br/>");
					}
					else
					{
						lyric.add_string("[This movement is an instrumental]<br/><br/>");
					}
				}

				convert_html_to_plain(lyric);
				p_data.add_item(lyric.get_ptr());

				continue;
			}

			// expression for extract lyrics
			regexp.Compile(regex_lyricbox, SINGLELINE);
			
			// match
			MatchResult result = regexp.Match(buff.get_ptr());

			// Get Group
			if (result.IsMatched())
			{
				int nStart = result.GetGroupStart(1);
				int nEnd = result.GetGroupEnd(1);

				pfc::string8 lyric(buff.get_ptr() + nStart, nEnd - nStart);
				convert_html_to_plain(lyric);
				p_data.add_item(lyric.get_ptr());

				continue;
			}
		}
		else
		{
			console_error(curl_easy_strerror(nCode));
		}

		p_data.add_item("");
	}

	// cleanup curl stuff
	curl_easy_cleanup(curl_handle);
}

void lyric_provider_lyricsplugin::query(metadb_handle_list_cref p_meta, pfc::string_list_impl & p_data, threaded_process_status & p_status, abort_callback & p_abort)
{
	// CURL Handle
	CURL * curl_handle;

	// Regular Expression Class
	CRegexpT<char> regexp;
	MatchResult match;

	// Buffer
	pfc::string8 buff;

	p_data.remove_all();
	p_status.set_title("Querying LyricsPlugin...");

	if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)
	{
		console_error("curl_global_init() faield");
		return;
	}

	// init curl session
	curl_handle = curl_easy_init();

	if (!curl_handle)
	{
		console_error("curl_easy_init() faield");
		return;
	}

	// Basic curl setting
	// Set Proxy
	if (m_config_item->proxy_type != PROXY_NONE)
	{
		curl_proxytype proxy_type;

		switch (m_config_item->proxy_type)
		{
		case PROXY_SOCKS4:
			proxy_type = CURLPROXY_SOCKS4;
			break;

		case PROXY_SOCKS5:
			proxy_type = CURLPROXY_SOCKS5;
			break;

		case PROXY_HTTP:
		default:
			proxy_type = CURLPROXY_HTTP;
			break;
		}

		// proxy type
		curl_easy_setopt(curl_handle, CURLOPT_PROXYTYPE, proxy_type);

		// proxy address:port
		curl_easy_setopt(curl_handle, CURLOPT_PROXY, m_config_item->proxy_hostport.get_ptr());

		if (m_config_item->proxy_need_auth)
		{
			pfc::string8_fast proxy = m_config_item->proxy_username;

			proxy.add_char(':');
			proxy.add_string(m_config_item->proxy_password);
			curl_easy_setopt(curl_handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
			curl_easy_setopt(curl_handle, CURLOPT_PROXYUSERPWD, proxy.get_ptr());
		}
	}

	// Set Timeout for connect
	// 10 sec
	curl_easy_setopt(curl_handle, CURLOPT_CONNECTTIMEOUT, 10);

	// Set low speed
	curl_easy_setopt(curl_handle, CURLOPT_LOW_SPEED_LIMIT, (long)0); 
	curl_easy_setopt(curl_handle, CURLOPT_LOW_SPEED_TIME, (long)15);

	// send all data to this function
	curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, g_write_memory_callback);

	// we pass our 'chunk' struct to the callback function 
	curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &buff);

	for (t_size i = 0; i < p_meta.get_count(); ++i)
	{
		if (p_abort.is_aborting())
			break;

		// Sleep 100ms;
		Sleep(100);

		// Clear buff
		buff.reset();

		const metadb_handle_ptr & p = p_meta.get_item(i);

		if (p.is_empty())
		{
			p_data.add_item("");
			continue;
		}

		// Set progress
		pfc::string8_fast path = file_path_canonical(p->get_path());

		// add subsong index?
		if (p->get_subsong_index() > 0)
		{
			path.add_string(" /index:");
			path.add_string(pfc::format_uint(p->get_subsong_index()));
		}

		p_status.set_item_path(path);
		p_status.set_progress(i + 1, p_meta.get_count());

		pfc::string8_fast artist, title;
		static_api_ptr_t<titleformat_compiler> compiler;
		service_ptr_t<titleformat_object> script;

		// Get ARTIST
		compiler->compile_safe(script, "[%artist%]");
		p->format_title(NULL, artist, script, NULL);
		// Get TITLE
		compiler->compile_safe(script, "[%title%]");
		p->format_title(NULL, title, script, NULL);

		// Fetching from HTTP
		// Set HTTP Address
		pfc::string8_fast url("http://www.lyricsplugin.com/winamp03/plugin/?artist=");
		char * pArtist = NULL, * pTitle = NULL;

		pArtist = curl_easy_escape(curl_handle, artist, NULL);
		pTitle = curl_easy_escape(curl_handle, title, NULL);

		url += pArtist;
		url += "&title=";
		url += pTitle;
		curl_free(pArtist);
		curl_free(pTitle);
		curl_easy_setopt(curl_handle, CURLOPT_URL, url.get_ptr());

		// Get it now
		CURLcode nCode = curl_easy_perform(curl_handle);

		if(nCode == CURLE_OK)
		{
			const char * regex_lyricbox = "<div\\s+id\\s*?=\\s*?\"lyrics\"\\s*?>[\\r\\n]*(.*?)[\\r\\n]*</div>";

			// expression for extract lyrics
			regexp.Compile(regex_lyricbox, SINGLELINE);

			// match
			MatchResult result = regexp.Match(buff.get_ptr());

			// Get Group
			if (result.IsMatched())
			{
				int nStart = result.GetGroupStart(1);
				int nEnd = result.GetGroupEnd(1);
				pfc::string8_fast lyric(buff.get_ptr() + nStart, nEnd - nStart);

				convert_html_to_plain(lyric);

				if (string_trim(lyric).get_length() > 0)
				{
					p_data.add_item(lyric);
					continue;
				}
			}
		}
		else
		{
			console_error(curl_easy_strerror(nCode));
		}

		p_data.add_item("");
	}

	// cleanup curl stuff
	curl_easy_cleanup(curl_handle);
}